package com.dhz.common.utils.http;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * http访问其他服务的工具类，类中的异常处理并不科学，有待改善
 *
 * @author FY
 */
public class HttpClientUtils {
    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
    private static final String DEFAULT_CHARSET = "UTF-8";
    private static final int DEFAULT_TIMEOUT = 5000;
    private static final int BUFFER_SIZE = 1024;
    private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36";

    private ConnectionConfig connConfig;
    private SocketConfig socketConfig;
    private ConnectionSocketFactory plainSF;
    private KeyStore trustStore;
    private SSLContext sslContext;
    private LayeredConnectionSocketFactory sslSF;
    private Registry<ConnectionSocketFactory> registry;
    private PoolingHttpClientConnectionManager connManager;
    private volatile BasicCookieStore cookieStore;
    private volatile HttpClient client;

    private static volatile HttpClientUtils instance;

    /**
     * 是有构造器，初始化属性
     */
    private HttpClientUtils(){
        //设置连接参数
        connConfig = ConnectionConfig.custom().setCharset(Charset.forName(DEFAULT_CHARSET)).build();
        socketConfig = SocketConfig.custom().setSoTimeout(10000).build();
        RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create();
        plainSF = new PlainConnectionSocketFactory();
        registryBuilder.register("http", plainSF);
        //指定信任密钥存储对象和连接套接字工厂
        try {
            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, new AnyTrustStrategy()).build();
            sslSF = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); //(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            registryBuilder.register("https", sslSF);
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            logger.warn("初始化HttpClient配置失败:", e);
        }

        registry = registryBuilder.build();
        //设置连接管理器
        connManager = new PoolingHttpClientConnectionManager(registry);
        connManager.setDefaultConnectionConfig(connConfig);
        connManager.setDefaultSocketConfig(socketConfig);
        //指定cookie存储对象
        cookieStore = new BasicCookieStore();
        //构建客户端
        client= HttpClientBuilder.create().setDefaultCookieStore(cookieStore).setUserAgent(USER_AGENT).setConnectionManager(connManager).build();
    }

    /**
     * 获取实例
     * @return {HttpClientUtils}
     */
    public static HttpClientUtils getInstance() {
        synchronized (HttpClientUtils.class) {
            if (instance == null) {
                instance = new HttpClientUtils();
            }
            return instance;
        }
    }


    // ****************************HTTP POST START***************************** //

    /**
     * 基础POST请求，传入查询参数，无Form参数;返回String数据
     * @param url {String}
     * @return {InputStream}
     * @throws IOException
     */
    public String doPost(String url) throws IOException, URISyntaxException {
        return readStream(doPostForStream(url), null);
    }

    /**
     * 基础POST请求，传入查询参数，无Form参数;返回InputStream数据
     * @param url {String}
     * @return {InputStream}
     * @throws IOException
     */
    public InputStream doPostForStream(String url) throws IOException, URISyntaxException {
        return doPostForStream(url, new HashMap<String, String>());
    }

    /**
     * 基础POST请求，传入查询参数，无Form参数;返回InputStream数据
     * @param url {String} 请求路径
     * @param queryParams {Map} 请求query参数
     * @return {String}
     * @throws IOException
     */
    public String doPost(String url, Map<String, String> queryParams) throws IOException, URISyntaxException {
        return readStream(doPostForStream(url, queryParams), null);
    }

    /**
     * 基础POST请求，传入查询参数，无Form参数;返回InputStream数据
     * @param url {String}
     * @param queryParams {Map}
     * @return {InputStream}
     * @throws IOException
     */
    public InputStream doPostForStream(String url, Map<String, String> queryParams) throws IOException, URISyntaxException {
        HttpResponse response = doPost(url, HttpClientUtils.paramsConverter(queryParams));
        return response==null?null:response.getEntity().getContent();
    }

    /**
     * 基础POST请求，传入查询参数，有Form参数;返回String数据
     * @param url {String} 请求路径
     * @param queryParams {Map} 请求query参数
     * @param formParams {Map} 请求form参数
     * @return {String}
     * @throws IOException
     */
    public String doPostForString(String url, Map<String, String> queryParams, Map<String, String> formParams) throws IOException, URISyntaxException {
        return readStream(doPostForStream(url, queryParams, formParams), null);
    }

    /**
     * 基础POST请求，传入查询参数，有Form参数;返回InputStream数据
     * @param url {String}
     * @param queryParams {Map} 请求query参数
     * @param formParams {Map} 请求form参数
     * @return {InputStream}
     * @throws IOException
     */
    public InputStream doPostForStream(String url, Map<String, String> queryParams, Map<String, String> formParams) throws IOException, URISyntaxException {
        HttpResponse response = doPost(url, HttpClientUtils.paramsConverter(queryParams), HttpClientUtils.paramsConverter(formParams));
        return response==null?null:response.getEntity().getContent();
    }

    /**
     * 基础POST请求，分别传入查询参数和Form参数
     * @param url {String}
     * @param queryParams {Map}
     * @param formParams {Map}
     * @return {HttpResponse}
     * @throws IOException
     */
    public HttpResponse doPost(String url, Map<String, String> queryParams, Map<String, String> formParams) throws IOException, URISyntaxException {
        return doPost(url, HttpClientUtils.paramsConverter(queryParams), HttpClientUtils.paramsConverter(formParams));
    }

    /**
     * 基础POST请求，分分别传入查询参数和Form参数
     * @param url {String}
     * @param queryParams {List}
     * @param formParams {List}
     * @return {HttpResponse}
     */
    public HttpResponse doPost(String url, List<NameValuePair> queryParams, List<NameValuePair> formParams) throws IOException, URISyntaxException {
        HttpPost pm = new HttpPost();
        try {
            URIBuilder builder = new URIBuilder(url);
            //填入查询参数
            if (queryParams != null && !queryParams.isEmpty()) {
                builder.addParameters(queryParams);
            }
            pm.setURI(builder.build());
            //填入表单参数
            if (formParams != null && !formParams.isEmpty()) {
                pm.setEntity(new UrlEncodedFormEntity(formParams, DEFAULT_CHARSET));
            }

            HttpResponse response = client.execute(pm);
            if (response.getStatusLine().getStatusCode() != 200) {
                throw new IOException(readStream(response.getEntity().getContent(), DEFAULT_CHARSET));
            }

            return response;
        } catch (URISyntaxException | IOException e) {
            logger.warn("网络请求异常：{}", url, e);
            throw e;
        }
    }

    /**
     * 基础POST请求，返回响应对象HttpResponse
     * @param url {String}
     * @param queryParams {List}
     * @return {HttpResponse}
     */
    public HttpResponse doPost(String url, List<NameValuePair> queryParams) throws URISyntaxException, IOException {
        HttpPost pm = new HttpPost();
        URIBuilder builder;
        try {
            builder = new URIBuilder(url);
            //填入查询参数
            if (queryParams!=null && !queryParams.isEmpty()){
                builder.addParameters(queryParams);
            }
            pm.setURI(builder.build());
            return client.execute(pm);
        } catch (URISyntaxException | IOException e) {
            logger.error("HttpClient doPost execute error: {}", e);
            throw e;
        }
    }

    // ****************************HTTP GET START***************************** //
    /**
     * GET请求，返回字符串，调用者需做null判断
     * @param url {String}
     * @return {String}
     * @throws IOException
     * @throws URISyntaxException
     */
    public String doGet(String url) throws IOException, URISyntaxException {
        return doGetForString(url, new HashMap<String, String>());
    }

    /**
     * GET请求，返回响应中的content stream，调用者需做null判断
     * @param url {String}
     * @return {InputStream}
     * @throws IOException
     * @throws URISyntaxException
     */
    public InputStream doGetForStream(String url) throws IOException, URISyntaxException {
        return this.doGetForStream(url, new HashMap<String, String>());
    }

    /**
     * GET请求，返回字符串，调用者需做null判断
     * @param url {String}
     * @param params {Map}
     * @return {String}
     */
    public String doGetForString(String url, Map<String, String> params) throws IOException, URISyntaxException {
        return HttpClientUtils.readStream(this.doGetForStream(url, params), null);
    }

    /**
     * GET请求，返回响应中的content stream，调用者需做null判断
     * @param url {String}
     * @param queryParams {Map}
     * @return {InputStream}
     */
    public InputStream doGetForStream(String url, Map<String, String> queryParams) throws IOException, URISyntaxException {
        HttpResponse response = this.doGet(url, queryParams);
        return response!=null ? response.getEntity().getContent() : null;
    }

    /**
     * 基本的Get请求，返回HTTP的Response对象，调用者需做null判断
     * @param url {String} 请求url
     * @param queryParams {Map<String, String>} 请求头的查询参数
     * @return {HttpResponse}
     */
    public HttpResponse doGet(String url, Map<String, String> queryParams) throws IOException, URISyntaxException {
        return doGet(url, HttpClientUtils.paramsConverter(queryParams));
    }

    /**
     * 基本的Get请求，返回HTTP的Response对象，调用者需做null判断
     * @param url {String} 请求url
     * @param queryParams {List<NameValuePair} 请求头的查询参数
     * @return {HttpResponse}
     */
    public HttpResponse doGet(String url, List<NameValuePair> queryParams) throws IOException, URISyntaxException {
        HttpGet get = new HttpGet();
        URIBuilder builder;
        try {
            builder = new URIBuilder(url);
            //填入查询参数
            if (queryParams!=null && !queryParams.isEmpty()){
                builder.addParameters(queryParams);
//                builder.setParameters(queryParams);
            }

            get.addHeader("X-Requested-With", "XMLHttpRequest");
            get.addHeader("Accept", "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01");

            get.setURI(builder.build());
            return client.execute(get);
        } catch (URISyntaxException | IOException e) {
            logger.error("HttpClient doPost execute error: {}", e);
            throw e;
        }

    }

    /**
     * 将流数据转换为String返回
     * @param in {InputStream}
     * @param charset {String}
     * @return String
     */
    public static String readStream(InputStream in, String charset) {
        if (in == null){
            return null;
        }
        try {
            InputStreamReader inReader = new InputStreamReader(in, StringUtils.isEmpty(charset)?DEFAULT_CHARSET:charset);
            char[] buffer= new char[BUFFER_SIZE];
            int readLen= 0;
            StringBuilder sb= new StringBuilder();
            while((readLen = inReader.read(buffer)) != -1){
                sb.append(buffer, 0, readLen);
            }
            inReader.close();
            return sb.toString();
        } catch (IOException e) {
            logger.error("读取返回内容出错", e);
        }
        return null;
    }

    /**
     * 将Map类型的数据转换为List<NameValuePair>类型的数据
     * @param params {Map}
     * @return List<NameValuePair>
     */
    private static List<NameValuePair> paramsConverter(Map<String, String> params){
        List<NameValuePair> nvps = new LinkedList<NameValuePair>();
        Set<Map.Entry<String, String>> paramsSet= params.entrySet();
        for (Map.Entry<String, String> paramEntry : paramsSet) {
            nvps.add(new BasicNameValuePair(paramEntry.getKey(), paramEntry.getValue()));
        }
        return nvps;
    }

}

class AnyTrustStrategy implements TrustStrategy {
    @Override
    public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        return true;
    }
}
